package com.matrix.product.service.impl;

import com.alibaba.nacos.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.matrix.api.merchant.MerchantApi;
import com.matrix.api.merchant.vo.ShopInfoVO;
import com.matrix.api.product.ProductApi;
import com.matrix.api.product.dto.ProductCategoryDTO;
import com.matrix.api.product.dto.ProductDTO;
import com.matrix.api.product.dto.SpecGroupDTO;
import com.matrix.api.product.dto.SpecOptionDTO;
import com.matrix.api.product.vo.*;
import com.matrix.common.constant.CacheConstants;
import com.matrix.common.constant.RedisConstants;
import com.matrix.common.constant.RocketMQConstants;
import com.matrix.common.domain.PageResult;
import com.matrix.common.exception.BusinessException;
import com.matrix.product.config.ThreadPoolConfig;
import com.matrix.product.converter.ProductConverter;
import com.matrix.product.entity.Product;
import com.matrix.product.entity.ProductCategory;
import com.matrix.product.entity.ProductSpecGroup;
import com.matrix.product.entity.ProductSpecOption;
import com.matrix.product.mapper.ProductCategoryMapper;
import com.matrix.product.mapper.ProductMapper;
import com.matrix.product.mapper.ProductSpecGroupMapper;
import com.matrix.product.mapper.ProductSpecOptionMapper;
import com.matrix.product.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcException;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.LongCodec;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 有点甜
 * @since 2025/7/27
 */
@Slf4j
@Service
@DubboService
public class ProductServiceImpl implements ProductService, ProductApi {

    private final ProductCategoryMapper categoryMapper;
    private final ProductMapper productMapper;
    private final ProductSpecGroupMapper specGroupMapper;
    private final ProductSpecOptionMapper specOptionMapper;
    private final StringRedisTemplate redisTemplate;
    private final CacheManager caffeineCacheManager;
    private final CacheManager redisCacheManager;
    private final RedissonClient redissonClient;
    private final ObjectMapper objectMapper;
    private final RocketMQTemplate rocketMQTemplate;
    private final Executor cacheRebuildExecutor;

    @DubboReference(check = false, timeout = 3000)
    private MerchantApi merchantApi;

    private static final String DEDUCT_STOCK_LUA_SCRIPT =
            "local stock = redis.call('get', KEYS[1]); " +
                    "if (not stock or tonumber(stock) < tonumber(ARGV[1])) then " +
                    "  return -1; " +
                    "end; " +
                    "return redis.call('decrby', KEYS[1], ARGV[1]);";

    public ProductServiceImpl(ProductCategoryMapper categoryMapper,
                              ProductMapper productMapper,
                              ProductSpecGroupMapper specGroupMapper,
                              ProductSpecOptionMapper specOptionMapper,
                              StringRedisTemplate redisTemplate,
                              @Qualifier("caffeineCacheManager") CacheManager caffeineCacheManager,
                              @Qualifier("redisCacheManager") CacheManager redisCacheManager,
                              RedissonClient redissonClient,
                              ObjectMapper objectMapper,
                              RocketMQTemplate rocketMQTemplate,
                              @Qualifier(ThreadPoolConfig.CACHE_REBUILD_EXECUTOR) Executor cacheRebuildExecutor) {
        this.categoryMapper = categoryMapper;
        this.productMapper = productMapper;
        this.specGroupMapper = specGroupMapper;
        this.specOptionMapper = specOptionMapper;
        this.redisTemplate = redisTemplate;
        this.caffeineCacheManager = caffeineCacheManager;
        this.redisCacheManager = redisCacheManager;
        this.redissonClient = redissonClient;
        this.objectMapper = objectMapper;
        this.rocketMQTemplate = rocketMQTemplate;
        this.cacheRebuildExecutor = cacheRebuildExecutor;
    }

    @Override
    public List<ProductCategoryVO> getShopMenu(Long shopId) {
        String cacheKey = RedisConstants.buildKey(RedisConstants.KEY_SHOP_MENU, shopId);

        Cache caffeineCache = caffeineCacheManager.getCache(CacheConstants.CACHE_NAME_MENU);
        if (caffeineCache != null) {
            Object cachedValue = caffeineCache.get(cacheKey, Object.class);
            if (cachedValue != null) {
                log.debug("一级缓存命中, shopId: {}", shopId);
                if (CacheConstants.CACHE_NULL_VALUE.equals(cachedValue)) {
                    return Collections.emptyList();
                }
                if (cachedValue instanceof List) {
                    try {
                        @SuppressWarnings("unchecked")
                        List<ProductCategoryVO> menu = (List<ProductCategoryVO>) cachedValue;
                        return menu;
                    } catch (ClassCastException e) {
                        log.error("Caffeine cache contains invalid type for key {}. Clearing cache.", cacheKey, e);
                        caffeineCache.evict(cacheKey);
                    }
                }
            }
        }

        Cache redisCache = redisCacheManager.getCache(CacheConstants.CACHE_NAME_MENU);
        if (redisCache != null) {
            String menuJson = redisCache.get(cacheKey, String.class);
            if (StringUtils.isNotBlank(menuJson)) {
                if (CacheConstants.CACHE_NULL_VALUE.equals(menuJson)) {
                    log.debug("二级缓存命中空值, shopId: {}", shopId);
                    if (caffeineCache != null) {
                        caffeineCache.put(cacheKey, CacheConstants.CACHE_NULL_VALUE);
                    }
                    return Collections.emptyList();
                }
                log.debug("二级缓存命中, shopId: {}", shopId);
                try {
                    List<ProductCategoryVO> menu = objectMapper.readValue(menuJson, new TypeReference<>() {});
                    if (caffeineCache != null) {
                        caffeineCache.put(cacheKey, menu);
                    }
                    return menu;
                } catch (Exception e) {
                    log.error("反序列化菜单缓存失败, shopId: {}, json: {}", shopId, menuJson, e);
                }
            }
        }

        String lockKey = RedisConstants.buildKey(RedisConstants.LOCK_SHOP_MENU_KEY_PREFIX, shopId);
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(10, TimeUnit.SECONDS)) {
                log.info("获取到分布式锁, 准备查询数据库, shopId: {}", shopId);
                if (redisCache != null) {
                    String menuJsonAfterLock = redisCache.get(cacheKey, String.class);
                    if (StringUtils.isNotBlank(menuJsonAfterLock)) {
                        return getShopMenu(shopId);
                    }
                }

                List<ProductCategoryVO> menuFromDB = getShopMenuFromDB(shopId);

                if (!menuFromDB.isEmpty()) {
                    String json = objectMapper.writeValueAsString(menuFromDB);
                    if (redisCache != null) {
                        redisCache.put(cacheKey, json);
                    }
                    if (caffeineCache != null) {
                        caffeineCache.put(cacheKey, menuFromDB);
                    }
                } else {
                    if (redisCache != null) {
                        redisCache.put(cacheKey, CacheConstants.CACHE_NULL_VALUE);
                    }
                    if (caffeineCache != null) {
                        caffeineCache.put(cacheKey, CacheConstants.CACHE_NULL_VALUE);
                    }
                }
                return menuFromDB;
            } else {
                log.warn("未获取到分布式锁, 稍后重试, shopId: {}", shopId);
                Thread.sleep(200);
                return getShopMenu(shopId);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("获取菜单线程被中断");
        } catch (Exception e) {
            log.error("获取店铺菜单失败, shopId: {}", shopId, e);
            throw new BusinessException("获取菜单失败");
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    @Transactional
    public ProductCategoryVO createCategory(Long userId, ProductCategoryDTO categoryDTO) {
        Long shopId = getShopIdByUserId(userId);
        ProductCategory category = ProductConverter.INSTANCE.toProductCategory(categoryDTO);
        category.setShopId(shopId);
        categoryMapper.insert(category);
        return ProductConverter.INSTANCE.toProductCategoryVO(category);
    }

    @Override
    @Transactional
    public ProductCategoryVO updateCategory(Long userId, Long categoryId, ProductCategoryDTO categoryDTO) {
        Long shopId = getShopIdByUserId(userId);
        ProductCategory category = findCategoryAndCheckAuth(categoryId, shopId);
        ProductConverter.INSTANCE.updateProductCategoryFromDTO(categoryDTO, category);
        categoryMapper.updateById(category);
        return ProductConverter.INSTANCE.toProductCategoryVO(category);
    }

    @Override
    public List<ProductCategoryVO> listCategories(Long userId) {
        Long shopId = getShopIdByUserId(userId);
        LambdaQueryWrapper<ProductCategory> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductCategory::getShopId, shopId)
                .orderByAsc(ProductCategory::getSortOrder);

        return ProductConverter.INSTANCE.toProductCategoryVOList(
                categoryMapper.selectList(queryWrapper)
        );
    }

    @Override
    @Transactional
    public void deleteCategory(Long userId, Long categoryId) {
        Long shopId = getShopIdByUserId(userId);
        findCategoryAndCheckAuth(categoryId, shopId);

        if (productMapper.selectCount(new LambdaQueryWrapper<Product>().eq(Product::getCategoryId, categoryId)) > 0) {
            throw new BusinessException("分类下存在菜品,无法删除");
        }
        categoryMapper.deleteById(categoryId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProductVO createProduct(Long userId, ProductDTO productDTO) {
        Long shopId = getShopIdByUserId(userId);
        findCategoryAndCheckAuth(productDTO.getCategoryId(), shopId);
        validateProductSpecs(productDTO.getSpecs());
        Product product = ProductConverter.INSTANCE.toProduct(productDTO);
        product.setShopId(shopId);
        product.setMonthlySales(0);
        productMapper.insert(product);
        saveSpecs(product.getId(), productDTO.getSpecs());
        return getProductDetails(userId, product.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProductVO updateProduct(Long userId, Long productId, ProductDTO productDTO) {
        Long shopId = getShopIdByUserId(userId);
        findProductAndCheckAuth(productId, shopId);
        findCategoryAndCheckAuth(productDTO.getCategoryId(), shopId);
        validateProductSpecs(productDTO.getSpecs());
        Product product = new Product();
        ProductConverter.INSTANCE.updateProductFromDTO(productDTO, product);
        productMapper.updateById(product);
        deleteSpecsByProductId(productId);
        saveSpecs(productId, productDTO.getSpecs());
        return getProductDetails(userId, productId);
    }

    @Override
    public PageResult<ProductVO> listProducts(Long userId, int pageNum, int pageSize, Long categoryId, String name) {
        Long shopId = getShopIdByUserId(userId);
        Page<Product> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Product::getShopId, shopId)
                .eq(categoryId != null, Product::getCategoryId, categoryId)
                .like(StringUtils.hasText(name), Product::getName, name)
                .orderByDesc(Product::getUpdateTime);

        productMapper.selectPage(page, queryWrapper);
        List<ProductVO> voList = ProductConverter.INSTANCE.toProductVOList(page.getRecords());
        Map<Long, String> categoryMap = listCategories(userId).stream()
                .collect(Collectors.toMap(ProductCategoryVO::getId, ProductCategoryVO::getName));
        voList.forEach(vo -> vo.setCategoryName(categoryMap.get(vo.getCategoryId())));
        return new PageResult<>(voList, page.getTotal(), page.getPages());
    }

    @Override
    public ProductVO getProductDetails(Long userId, Long productId) {
        Long shopId = getShopIdByUserId(userId);
        Product product = findProductAndCheckAuth(productId, shopId);
        ProductCategory category = categoryMapper.selectById(product.getCategoryId());
        ProductVO vo = ProductConverter.INSTANCE.toProductVO(product);
        vo.setCategoryName(category != null ? category.getName() : "未分类");
        vo.setSpecs(getSpecsByProductId(productId));
        return vo;
    }

    @Override
    @Transactional
    public void deleteProduct(Long userId, Long productId) {
        Long shopId = getShopIdByUserId(userId);
        findProductAndCheckAuth(productId, shopId);
        deleteSpecsByProductId(productId);
        productMapper.deleteById(productId);
    }

    @Override
    public List<ProductVO> getProductsByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return Collections.emptyList();
        }
        List<Product> products = productMapper.selectBatchIds(ids);
        List<ProductVO> voList = ProductConverter.INSTANCE.toProductVOList(products);
        if (products.isEmpty()) {
            return Collections.emptyList();
        }
        List<Long> categoryIds = products.stream().map(Product::getCategoryId).distinct().toList();

        Map<Long, String> categoryMap = categoryMapper.selectBatchIds(categoryIds).stream()
                .collect(Collectors.toMap(ProductCategory::getId, ProductCategory::getName));

        Map<Long, List<SpecGroupVO>> specsByProduct = getSpecsForProductList(ids).stream()
                .collect(Collectors.groupingBy(SpecGroupVO::getProductId));

        voList.forEach(vo -> {
            vo.setCategoryName(categoryMap.get(vo.getCategoryId()));
            vo.setSpecs(specsByProduct.getOrDefault(vo.getId(), Collections.emptyList()));
        });
        return voList;
    }

    @Override
    @Transactional
    public void batchUpdateProductRatings(Map<Long, BigDecimal> ratingsMap) {
        if (ratingsMap == null || ratingsMap.isEmpty()) {
            return;
        }
        productMapper.batchUpdateRatings(ratingsMap);
        log.info("已批量更新 {} 个菜品的评分。", ratingsMap.size());
    }

    @Override
    @Transactional
    public void batchUpdateMonthlySales(Map<Long, Integer> salesMap) {
        if (salesMap == null || salesMap.isEmpty()) {
            return;
        }
        productMapper.batchUpdateMonthlySales(salesMap);
        log.info("已批量更新 {} 个菜品的月销量。", salesMap.size());
    }

    @Override
    @Transactional
    public void deductStock(Map<Long, Integer> stockDeductionMap) {
        for (Map.Entry<Long, Integer> entry : stockDeductionMap.entrySet()) {
            Long productId = entry.getKey();
            Integer quantity = entry.getValue();
            String stockKey = RedisConstants.buildKey(RedisConstants.PRODUCT_STOCK_KEY_PREFIX, productId);

            RScript script = redissonClient.getScript(LongCodec.INSTANCE);
            Long result = script.eval(RScript.Mode.READ_WRITE,
                    DEDUCT_STOCK_LUA_SCRIPT,
                    RScript.ReturnType.INTEGER,
                    Collections.singletonList(stockKey),
                    quantity);

            if (result == -1) {
                Product product = productMapper.selectById(productId);
                String productName = (product != null) ? product.getName() : "ID:" + productId;
                throw new BusinessException("菜品 [" + productName + "] 库存不足");
            }

            log.info("成功扣减 Redis 库存, ProductId: {}, 数量: {}", productId, quantity);
        }
        rocketMQTemplate.convertAndSend(RocketMQConstants.PRODUCT_STOCK_DEDUCT_TOPIC, stockDeductionMap);
        log.info("已发送扣减库存消息, 数量: {}", stockDeductionMap.size());
    }

    @Override
    @Transactional
    public void increaseStock(Map<Long, Integer> stockIncreaseMap) {
        for (Map.Entry<Long, Integer> entry : stockIncreaseMap.entrySet()) {
            Long productId = entry.getKey();
            Integer quantity = entry.getValue();
            String stockKey = RedisConstants.buildKey(RedisConstants.PRODUCT_STOCK_KEY_PREFIX, productId);

            redisTemplate.opsForValue().increment(stockKey, quantity);
            productMapper.update(null,
                    new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<Product>()
                            .eq(Product::getId, entry.getKey())
                            .setSql("stock = stock + " + entry.getValue())
            );
            log.info("成功返还 Redis 库存, ProductId: {}, 数量: {}", productId, quantity);
        }
    }

    private List<ProductCategoryVO> getShopMenuFromDB(Long shopId) {
        LambdaQueryWrapper<ProductCategory> categoryWrapper = new LambdaQueryWrapper<>();
        categoryWrapper.eq(ProductCategory::getShopId, shopId).orderByAsc(ProductCategory::getSortOrder);
        List<ProductCategory> categories = categoryMapper.selectList(categoryWrapper);
        if (categories.isEmpty()) {
            return Collections.emptyList();
        }
        List<Long> categoryIds = categories.stream().map(ProductCategory::getId).toList();

        CompletableFuture<List<Product>> productsFuture = CompletableFuture.supplyAsync(() -> {
            LambdaQueryWrapper<Product> productWrapper = new LambdaQueryWrapper<>();
            productWrapper.in(Product::getCategoryId, categoryIds).eq(Product::getStatus, 1);
            return productMapper.selectList(productWrapper);
        }, cacheRebuildExecutor);

        CompletableFuture<List<SpecGroupVO>> specsFuture = productsFuture.thenComposeAsync(products -> {
            if (products.isEmpty()) {
                return CompletableFuture.completedFuture(Collections.emptyList());
            }
            List<Long> productIds = products.stream().map(Product::getId).toList();
            return CompletableFuture.supplyAsync(() -> getSpecsForProductList(productIds), cacheRebuildExecutor);
        }, cacheRebuildExecutor);

        CompletableFuture.allOf(productsFuture, specsFuture).join();

        try {
            List<Product> products = productsFuture.get();
            List<SpecGroupVO> allSpecs = specsFuture.get();
            Map<Long, List<Product>> productsByCategory = products.stream().collect(Collectors.groupingBy(Product::getCategoryId));
            Map<Long, List<SpecGroupVO>> specsByProduct = allSpecs.stream().collect(Collectors.groupingBy(SpecGroupVO::getProductId));

            return categories.stream().map(category -> {
                ProductCategoryVO categoryVO = ProductConverter.INSTANCE.toProductCategoryVO(category);
                List<Product> productList = productsByCategory.getOrDefault(category.getId(), Collections.emptyList());
                List<ProductVO> productVOs = productList.stream().map(product -> {
                    ProductVO productVO = ProductConverter.INSTANCE.toProductVO(product);
                    productVO.setCategoryName(category.getName());
                    productVO.setSpecs(specsByProduct.getOrDefault(product.getId(), Collections.emptyList()));
                    return productVO;
                }).toList();
                categoryVO.setProducts(productVOs);
                return categoryVO;
            }).toList();
        } catch (Exception e) {
            log.error("在异步任务完成后组装菜单失败, shopId: {}", shopId, e);
            throw new BusinessException("加载菜单数据失败");
        }
    }

    private void validateProductSpecs(List<SpecGroupDTO> specs) {
        if (specs == null || specs.isEmpty()) {
            return;
        }

        if (specs.size() > 5) {
            throw new BusinessException("一个菜品最多只能添加5个规格组");
        }

        for (SpecGroupDTO group : specs) {
            if (group.getOptions() == null || group.getOptions().isEmpty()) {
                throw new BusinessException("规格组 [" + group.getName() + "] 必须包含至少一个规格选项");
            }

            if (group.getOptions().size() > 7) {
                throw new BusinessException("规格组 [" + group.getName() + "] 最多只能添加7个规格选项");
            }
        }
    }

    private void saveSpecs(Long productId, List<SpecGroupDTO> specs) {
        if (specs == null || specs.isEmpty()) {
            return;
        }
        for (SpecGroupDTO groupDTO : specs) {
            ProductSpecGroup group = new ProductSpecGroup();
            group.setProductId(productId);
            group.setName(groupDTO.getName());
            specGroupMapper.insert(group);
            Long specGroupId = group.getId();
            if (groupDTO.getOptions() != null) {
                for (SpecOptionDTO optionDTO : groupDTO.getOptions()) {
                    ProductSpecOption option = new ProductSpecOption();
                    option.setSpecGroupId(specGroupId);
                    option.setName(optionDTO.getName());
                    option.setPriceModifier(optionDTO.getPriceModifier());
                    specOptionMapper.insert(option);
                }
            }
        }
    }

    private Long getShopIdByUserId(Long userId) {
        try {
            ShopInfoVO shopInfo = merchantApi.getShopInfoByUserId(userId);
            if (shopInfo == null) {
                throw new BusinessException("您还没有创建店铺,请先完成入驻");
            }
            return shopInfo.getShopId();
        } catch (RpcException e) {
            log.error("调用商家服务获取店铺ID失败, userId: {}", userId, e);
            throw new BusinessException("商家服务暂不可用,请稍后重试");
        }
    }

    private ProductCategory findCategoryAndCheckAuth(Long categoryId, Long shopId) {
        ProductCategory category = categoryMapper.selectById(categoryId);
        if (category == null || !category.getShopId().equals(shopId)) {
            throw new BusinessException("分类不存在或无权操作");
        }
        return category;
    }

    private Product findProductAndCheckAuth(Long productId, Long shopId) {
        Product product = productMapper.selectById(productId);
        if (product == null || !product.getShopId().equals(shopId)) {
            throw new BusinessException("菜品不存在或无权操作");
        }
        return product;
    }

    private List<SpecGroupVO> getSpecsByProductId(Long productId) {
        List<ProductSpecGroup> groups = specGroupMapper.selectList(new LambdaQueryWrapper<ProductSpecGroup>()
                .eq(ProductSpecGroup::getProductId, productId));
        if (groups.isEmpty()) {
            return Collections.emptyList();
        }
        List<Long> groupIds = groups.stream().map(ProductSpecGroup::getId).toList();
        List<ProductSpecOption> options = specOptionMapper.selectList(new LambdaQueryWrapper<ProductSpecOption>()
                .in(ProductSpecOption::getSpecGroupId, groupIds));
        Map<Long, List<ProductSpecOption>> optionsMap = options.stream()
                .collect(Collectors.groupingBy(ProductSpecOption::getSpecGroupId));
        return groups.stream().map(g -> {
            SpecGroupVO groupVO = ProductConverter.INSTANCE.toSpecGroupVO(g);
            List<SpecOptionVO> optionVOs = optionsMap.getOrDefault(g.getId(), Collections.emptyList())
                    .stream()
                    .map(ProductConverter.INSTANCE::toSpecOptionVO)
                    .toList();
            groupVO.setOptions(optionVOs);
            return groupVO;
        }).toList();
    }

    private void deleteSpecsByProductId(Long productId) {
        List<Long> groupIds = specGroupMapper.selectList(new LambdaQueryWrapper<ProductSpecGroup>()
                        .eq(ProductSpecGroup::getProductId, productId))
                .stream().map(ProductSpecGroup::getId).toList();
        if (!groupIds.isEmpty()) {
            specOptionMapper.delete(new LambdaQueryWrapper<ProductSpecOption>().in(ProductSpecOption::getSpecGroupId, groupIds));
        }
        specGroupMapper.delete(new LambdaQueryWrapper<ProductSpecGroup>().eq(ProductSpecGroup::getProductId, productId));
    }

    private List<SpecGroupVO> getSpecsForProductList(List<Long> productIds) {
        if(productIds.isEmpty()) {
            return Collections.emptyList();
        }
        List<ProductSpecGroup> groups = specGroupMapper.selectList(
                new LambdaQueryWrapper<ProductSpecGroup>().in(ProductSpecGroup::getProductId, productIds));
        if (groups.isEmpty()) {
            return Collections.emptyList();
        }
        List<Long> groupIds = groups.stream().map(ProductSpecGroup::getId).toList();
        List<ProductSpecOption> options = specOptionMapper.selectList(
                new LambdaQueryWrapper<ProductSpecOption>().in(ProductSpecOption::getSpecGroupId, groupIds));
        Map<Long, List<ProductSpecOption>> optionsMap = options.stream()
                .collect(Collectors.groupingBy(ProductSpecOption::getSpecGroupId));

        return groups.stream().map(g -> {
            SpecGroupVO groupVO = ProductConverter.INSTANCE.toSpecGroupVO(g);
            List<SpecOptionVO> optionVOs = optionsMap.getOrDefault(g.getId(), Collections.emptyList())
                    .stream()
                    .map(ProductConverter.INSTANCE::toSpecOptionVO)
                    .toList();
            groupVO.setOptions(optionVOs);
            return groupVO;
        }).toList();
    }
}